package com.liuya.safe.client.service.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.liuya.common.exception.EntityExistException;
import com.liuya.db.util.DBUtil;
import com.liuya.safe.client.dao.AppMapper;
import com.liuya.safe.client.dao.AppUserTypeMapper;
import com.liuya.safe.client.pojo.*;
import com.liuya.safe.client.service.ApplicationManager;
import com.liuya.safe.client.service.UserTypeManager;
import com.liuya.safe.entitle.service.PrivilegeManagerService;
import com.liuya.safe.model.SafeUser;
import com.liuya.safe.policy.service.QueryManagerService;
import com.liuya.safe.policy.service.UserCategoryManagerService;
import com.liuya.safe.system.role.service.impl.RoleManagerServiceImpl;
import com.liuya.safe.util.Factory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.liuya.db.DBLevelException;
import com.liuya.db.DBPower;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("applicationManager")
public class ApplicationManagerImpl implements ApplicationManager {
    private static Log logger = LogFactory.getLog(ApplicationManagerImpl.class);
    @Autowired
    AppMapper appMapper;
    @Autowired
    AppUserTypeMapper appUserTypeMapper;

    private Map store = new HashMap();
    private boolean changed = true;

    public ApplicationManagerImpl() {
    }

    private synchronized void loadIntoMemory() {
        if (!changed) {
            return;
        }

        Collection allApps = appMapper.selectAll();
        Collection allAppUserTypes = appUserTypeMapper.selectAll();

        store.clear();

        for (Iterator iter = allApps.iterator(); iter.hasNext();) {
            Application app = (Application) iter.next();
            store.put(app.getName(), app);
        }

        for (Iterator iter = allAppUserTypes.iterator(); iter.hasNext();) {
            AppUserType entry = (AppUserType) iter.next();
            String appName = entry.getAppName();

            UserType userType = extractUserType(entry);

            Application app = (Application) store.get(appName);
            app.getUserTypes().add(userType);
        }
        changed = false;
    }

    public void addApplication(Locale locale, Application app) throws EntityExistException {
        loadIntoMemory();

        // Already exist?
        if (store.keySet().contains(app.getName())) {
            throw new EntityExistException("The name '" + app.getName() + "' already exists.");
        }

        try {

            appMapper.insert(app);
            // save usertypes info
            Collection userTypes = app.getUserTypes();
            if (userTypes != null && userTypes.size() > 0) {
                List appUserTypes = new ArrayList(userTypes.size());
                for (Iterator iter = userTypes.iterator(); iter.hasNext();) {
                    UserType userType = (UserType) iter.next();

                    AppUserType appUserType = new AppUserType();
                    appUserType.setAppName(app.getName());
                    appUserType.setUserTypeName(userType.getName());
                    String produceUserMetadataStr = produceUserMetadataStr(userType.getUserMetadata());
                    appUserType.setUserMetadataStr(produceUserMetadataStr);

                    appUserTypes.add(appUserType);
                    appUserTypeMapper.insert(appUserType);
                }
            }

        } catch (Exception e) {
            throw new DBLevelException(e);
        } finally {
        }

        changed = true;

        // create tables
        createTablesForApp(app);

        Iterator itr = app.getUserTypes().iterator();
        while (itr.hasNext()) {
            UserType userType = (UserType) itr.next();
            // create reserved query
            QueryManagerService queryManager = Factory.getQueryManager(app.getName());
            queryManager.addReservedQuery(userType.getName());

            // create reserved usercategory
            UserCategoryManagerService userCategoryManager = Factory.getUserCategoryManager(app.getName());
            userCategoryManager.addReservedUserCategory(locale);

            // create reserved privilege
            PrivilegeManagerService privilegeManager = Factory.getPrivilegeManager(app.getName());
            privilegeManager.addReservedPrivilege(locale);

            // create reserved roles
            RoleManagerServiceImpl roleManager = Factory.getRoleManager(app.getName());
            roleManager.addReservedRole(locale);
        }
    }

    private void createTablesForApp(Application app) {

        // Name rule for role, privilege and roleprivilege is:
        // <appName>_role, <appName>_privilege and <appName>_roleprivilege
        String appName = app.getName();
        String sqlRole = DBUtil.roleTableCreateSql(appName);
        String sqlPrivilege = DBUtil.privilegeTableCreateSql(appName);
        String sqlRolePrivilege = DBUtil.rolePrivilegeTableCreateSql(appName);

        // Each usertype has it's own userrole table. Name rule is:
        // <appName>_<userTypeName>_userrole
        ArrayList sqlUserRoles = new ArrayList();
        Iterator itr = app.getUserTypes().iterator();
        while (itr.hasNext()) {
            UserType userType = (UserType) itr.next();
            userType = Factory.getUserTypeManager().getUserType(userType.getName());
            TableMetadata userTable = userType.getUserMetadata().getMainTableMetadata();
            String sqlUserRole = DBUtil.userRoleTableCreateSql(appName, userType.getName(), SafeUser.idFieldName, getIdColumnType(userTable));
            sqlUserRoles.add(sqlUserRole);
        }

        String tableQueryCreateSql = DBUtil.tableQueryCreateSql(appName);
        String tableUserCategoryCreateSql = DBUtil.tableUserCategoryCreateSql(appName);
        String tableDicisionEntitlementCreateSql = DBUtil.tableDecisionEntitlementCreateSql(appName);
        String tableQueryEntitlementCreateSql = DBUtil.tableQueryEntitlementCreateSql(appName);
        String tableBusinessDataCreateSql = DBUtil.tableBusinessDataCreateSql(appName);

        Connection conn = DBPower.getConnectionByName(DBPower.getDefaultDsName());
        try {
            // conn.setAutoCommit(false);
            // create tables
            DBUtil.exec(conn, sqlRole);
            DBUtil.exec(conn, sqlPrivilege);
            DBUtil.exec(conn, sqlRolePrivilege);
            itr = sqlUserRoles.iterator();
            while (itr.hasNext()) {
                DBUtil.exec(conn, (String) itr.next());
            }
            DBUtil.exec(conn, tableQueryCreateSql);
            DBUtil.exec(conn, tableUserCategoryCreateSql);
            DBUtil.exec(conn, tableDicisionEntitlementCreateSql);
            DBUtil.exec(conn, tableQueryEntitlementCreateSql);
            DBUtil.exec(conn, tableBusinessDataCreateSql);
            // conn.commit();
        } catch (SQLException e) {
            // try {
            // conn.rollback();
            // } catch (SQLException e1) {
            // throw new DBLevelException(e1);
            // }
            throw new DBLevelException(e);
        } finally {
            DBUtil.close(conn);
        }
    }

    public void addAppUserType(Locale locale, String appName, UserType userType) {
        loadIntoMemory();

        // 1. save ApplicationUserType info
        AppUserType appUserType = new AppUserType();
        appUserType.setAppName(appName);
        appUserType.setUserTypeName(userType.getName());
        String produceUserMetadataStr = produceUserMetadataStr(userType.getUserMetadata());
        appUserType.setUserMetadataStr(produceUserMetadataStr);

        try {
            appUserTypeMapper.insert(appUserType);
        } catch (Exception e2) {
            throw new DBLevelException(e2);
        }

        changed = true;

        // 2. create related userrole tables of application's userType
        Connection conn = DBPower.getConnectionByName(DBPower.getDefaultDsName());
        try {
            conn.setAutoCommit(false);
            userType = Factory.getUserTypeManager().getUserType(userType.getName());
            TableMetadata userTable = userType.getUserMetadata().getMainTableMetadata();
            DBUtil.exec(conn, DBUtil.userRoleTableCreateSql(appName, userType.getName(), SafeUser.idFieldName, getIdColumnType(userTable)));
            conn.commit();
        } catch (SQLException e) {
            try {
                conn.rollback();
            } catch (SQLException e1) {
                throw new DBLevelException(e1);
            }
            throw new DBLevelException(e);
        } finally {
            DBUtil.close(conn);
        }

        // 3. create reserved query
        QueryManagerService queryManager = Factory.getQueryManager(appName);
        queryManager.addReservedQuery(userType.getName());

        // 4. create reserved usercategory
        UserCategoryManagerService userCategoryManager = Factory.getUserCategoryManager(appName);
        userCategoryManager.addReservedUserCategory(locale);

        // 5. create reserved privilege
        PrivilegeManagerService privilegeManager = Factory.getPrivilegeManager(appName);
        privilegeManager.addReservedPrivilege(locale);

        // 6. create reserved roles
        RoleManagerServiceImpl roleManager = Factory.getRoleManager(appName);
        roleManager.addReservedRole(locale);
    }

    private String getIdColumnType(TableMetadata userTable) {
        String idColumnType = null;
        FieldMetadata[] fields = userTable.getFields();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName().equals(SafeUser.idFieldName)) {
                idColumnType = fields[i].getSqlType();
                break;
            }
        }
        return idColumnType;
    }

    private String produceUserMetadataStr(UserMetadata userMetadata) {
        StringBuffer buf = new StringBuffer();
        FieldMetadata[] fields = userMetadata.getMainTableMetadata().getFields();
        for (int i = 0; i < fields.length; i++) {
            buf.append(fields[i].getColumnName() + " ");
        }
        return buf.toString();
    }

    private UserType extractUserType(AppUserType appUserType) {
        String[] columNames = appUserType.getUserMetadataStr().split(" ");
        UserTypeManager userTypeManager = Factory.getUserTypeManager();
        UserType userType = userTypeManager.getUserTypeCopy(appUserType.getUserTypeName());
        FieldMetadata[] fields = userType.getUserMetadata().getMainTableMetadata().getFields();
        // find usertype infos which defined in usertype definition
        ArrayList newFields = new ArrayList();
        for (int i = 0; i < columNames.length; i++) {
            for (int j = 0; j < fields.length; j++) {
                if (columNames[i].equals(fields[j].getColumnName())) {
                    newFields.add(fields[j]);
                }
            }
        }
        userType.getUserMetadata().getMainTableMetadata().setFields((FieldMetadata[]) newFields.toArray(new FieldMetadata[newFields.size()]));
        return userType;
    }

    public void deleteApplication(String name) {
        Application app = getApplication(name);
        try {

            AppUserType appUserType = new AppUserType();
            appUserType.setAppName(name);
            appUserTypeMapper.deleteByName(appUserType);

            Application hint = new Application();
            hint.setName(name);
            appMapper.deleteByName(hint);
        } catch (Exception e) {
            throw new DBLevelException(e);
        } finally {
        }

        changed = true;

        // delete tables
        deleteTablesForApp(app);

        Factory.applicationChanged(app.getName());
    }

    private void deleteTablesForApp(Application app) {
        // Name rule for role, privilegeand roleprivilege table is:
        // <appName>_role,<appName>_privilege and<appName>_roleprivilege
        String appName = app.getName();
        String sqlRole = DBUtil.roleTableDropSql(appName);
        String sqlPrivilege = DBUtil.privilegeTableDropSql(appName);
        String sqlRolePrivilege = DBUtil.rolePrivilegeTableDropSql(appName);

        // userrole tables
        ArrayList sqlUserRoles = new ArrayList();
        Iterator itr = app.getUserTypes().iterator();
        while (itr.hasNext()) {
            UserType userType = (UserType) itr.next();
            String sqlUserRole = DBUtil.userRoleTableDropSql(appName, userType.getName());
            sqlUserRoles.add(sqlUserRole);
        }

        String tableQueryDropSql = DBUtil.tableQueryDropSql(appName);
        String tableUserCategoryDropSql = DBUtil.tableUserCategoryDropSql(appName);
        String tableDicisionEntitlementDropSql = DBUtil.tableDecisionEntitlementDropSql(appName);
        String tableQueryEntitlementDropSql = DBUtil.tableQueryEntitlementDropSql(appName);
        String tableBusinessDataDropSql = DBUtil.tableBusinessDataDropSql(appName);

        Connection conn = DBPower.getConnectionByName(DBPower.getDefaultDsName());
        try {
            conn.setAutoCommit(false);
            DBUtil.exec(conn, tableQueryDropSql);
            DBUtil.exec(conn, tableUserCategoryDropSql);
            DBUtil.exec(conn, tableDicisionEntitlementDropSql);
            DBUtil.exec(conn, tableQueryEntitlementDropSql);
            DBUtil.exec(conn, tableBusinessDataDropSql);

            itr = sqlUserRoles.iterator();
            while (itr.hasNext()) {
                String sql = (String) itr.next();
                try {
                    DBUtil.exec(conn, sql);
                } catch (SQLException e) {
                    logger.error("Error when delete table: " + sql);
                }
            }
            DBUtil.exec(conn, sqlRolePrivilege);
            DBUtil.exec(conn, sqlRole);
            DBUtil.exec(conn, sqlPrivilege);

            conn.commit();
        } catch (SQLException e) {
            try {
                conn.rollback();
            } catch (SQLException e1) {
                throw new DBLevelException(e1);
            }
            throw new DBLevelException(e);
        } finally {
            DBUtil.close(conn);
        }
    }

    public void deleteAppUserType(String appName, String userTypeName) {
        Connection conn = null;
        boolean autoCommit = true;
        try {
            conn = DBPower.getConnectionByName("");
            autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);

            // delete ApplicationUserType infos
            AppUserType hint = new AppUserType();
            hint.setAppName(appName);
            hint.setUserTypeName(userTypeName);
            appUserTypeMapper.deleteByName(hint);

            // drop userrole tables
            DBUtil.exec(conn, DBUtil.userRoleTableDropSql(appName, userTypeName));

            conn.commit();
        } catch (SQLException e) {
            throw new DBLevelException(e);
        } finally {
            DBUtil.setCommitMode(conn, autoCommit);
            DBUtil.close(conn);
        }

        changed = true;
    }

    public Collection getAllApplications() {
        loadIntoMemory();
        return store.values();
    }

    public Application getApplication(String name) {
        loadIntoMemory();
        return (Application) store.get(name);
    }

    public void updateApplication(Application app) {
        loadIntoMemory();

        Application originalApplication = getApplication(app.getName());
        if (originalApplication == null) {
            return;
        }

        // get a copy of original application
        Collection origUserTypes = originalApplication.getUserTypes();
        Collection newUserTypes = app.getUserTypes();

        Connection conn = null;
        boolean autoCommit = true;
        try {
            conn = DBPower.getConnectionByName("");
            autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);

            // delete original app-usertype
            AppUserType hintUserType = new AppUserType();
            hintUserType.setAppName(app.getName());
            appUserTypeMapper.deleteByName(hintUserType);

            // save updated app-usertype relationship
            Collection userTypes = app.getUserTypes();
            if (userTypes != null && userTypes.size() > 0) {
                List appUserTypes = new ArrayList(userTypes.size());
                for (Iterator iter = userTypes.iterator(); iter.hasNext();) {
                    UserType userType = (UserType) iter.next();

                    AppUserType appUserType = new AppUserType();
                    appUserType.setAppName(app.getName());
                    appUserType.setUserTypeName(userType.getName());
                    String produceUserMetadataStr = produceUserMetadataStr(userType.getUserMetadata());
                    appUserType.setUserMetadataStr(produceUserMetadataStr);

                    appUserTypes.add(appUserType);

                    appUserTypeMapper.insert(appUserType);

                }
            }

            // update app info
            try {
                appMapper.updateByName(app);
            } catch (Exception e) {
                e.printStackTrace();
                throw new DBLevelException(e);
            }

            // update userrole tables
            // 1, find out which usertype(s) to be newly added and which to be
            // deleted
            Iterator origItr = origUserTypes.iterator();
            while (origItr.hasNext()) {
                UserType origUserType = (UserType) origItr.next();
                Iterator newItr = newUserTypes.iterator();
                while (newItr.hasNext()) {
                    UserType newUserType = (UserType) newItr.next();
                    if (origUserType.getName().equals(newUserType.getName())) {
                        // this usertype is updated, move out
                        origItr.remove();
                        newItr.remove();
                    }
                }
            }

            Collection userTypesToBeDeleted = origUserTypes;
            Collection userTypesToBeAdded = newUserTypes;

            // 2, delete related userrole tables

            Iterator delItr = userTypesToBeDeleted.iterator();
            while (delItr.hasNext()) {
                UserType delUserType = (UserType) delItr.next();
                DBUtil.exec(conn, DBUtil.userRoleTableDropSql(app.getName(), delUserType.getName()));
            }

            // 3, create related userrole tables

            Iterator addItr = userTypesToBeAdded.iterator();
            while (addItr.hasNext()) {
                UserType delUserType = (UserType) addItr.next();
                delUserType = Factory.getUserTypeManager().getUserType(delUserType.getName());
                TableMetadata userTable = delUserType.getUserMetadata().getMainTableMetadata();
                DBUtil.exec(conn, DBUtil.userRoleTableCreateSql(app.getName(), delUserType.getName(), SafeUser.idFieldName, getIdColumnType(userTable)));
            }

            conn.commit();
        } catch (SQLException e) {
            DBUtil.rollback(conn);
            throw new DBLevelException(e);
        } finally {
            DBUtil.setCommitMode(conn, autoCommit);
            DBUtil.close(conn);
        }

        changed = true;

        // notify Factory
        Factory.applicationChanged(app.getName());
    }

    public void updateApplicatonUserType(String appName, UserType userType) {
        Connection conn = null;
        boolean autoCommit = true;
        try {
            conn = DBPower.getConnectionByName("");
            autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);

            AppUserType appUserType = new AppUserType();
            appUserType.setAppName(appName);
            appUserType.setUserTypeName(userType.getName());
            String userMetadataStr = produceUserMetadataStr(userType.getUserMetadata());
            appUserType.setUserMetadataStr(userMetadataStr);

            // delete original app-usertype
            appUserTypeMapper.deleteByName(appUserType);

            // save newly app-usertype
            try {
                appUserTypeMapper.insert(appUserType);
            } catch (Exception e) {
                // should not happen
                e.printStackTrace();
                throw new DBLevelException(e);
            }

            conn.commit();
        } catch (SQLException e) {
            DBUtil.rollback(conn);
            throw new DBLevelException(e);
        } finally {
            DBUtil.setCommitMode(conn, autoCommit);
            DBUtil.close(conn);
        }

        changed = true;

        // notify Factory
        Factory.applicationUserTypeChanged(appName, userType.getName());
    }
}
